package pers.mor.tomcat.http;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import pers.mor.tomcat.config.WebXML;
import pers.mor.tomcat.listener.SessionEvent;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionListener;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class SessionManager {

    private static ConcurrentHashMap<String, HttpSession> currentSessionMap = new ConcurrentHashMap<>();

    private static Map<String, List<HttpSessionListener>> listenerMap = new HashMap<>();

    private static ConcurrentHashMap<String, String> contextPath_jsessionid_currentMap = new ConcurrentHashMap<>();

    private static ConcurrentHashMap<String, String> jsessionid_contextPath_currentMap = new ConcurrentHashMap<>();

    private static int defaultTimeout = getTimeout();

    private static int getTimeout() {
        int defaultTime = 30;
        int timeout = WebXML.sessionTimeout();
        if (timeout != 0) {
            defaultTime = timeout;
        }
        return defaultTime;
    }

    static {
        startSessionTimeOutCheckThread();
    }

    private static void startSessionTimeOutCheckThread() {
        new Thread(){
            @Override
            public void run() {
                try {
                    sessionTimeOutCheck();
                    Thread.sleep(1000 * 30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private static void sessionTimeOutCheck() {

        List<String> list = new ArrayList<>();

        Enumeration<String> keys = currentSessionMap.keys();
        while (keys.hasMoreElements()) {
            String id = keys.nextElement();
            HttpSession session = currentSessionMap.get(id);
            long timeMillis = System.currentTimeMillis();
            long lastTimeMillis = session.getLastAccessedTime();
            int maxInterval = session.getMaxInactiveInterval();
            if (timeMillis - lastTimeMillis >= maxInterval * 1000 *60) {
                list.add(id);
                triggerListener("destroy", session);
            }
        }
        for (String id : list) {
            currentSessionMap.remove(id);
        }
    }

    public static void addListener(String contextPath, HttpSessionListener listener) {
        List<HttpSessionListener> list = listenerMap.get(contextPath);
        if (list == null) {
            List<HttpSessionListener> listenerList = new ArrayList<>();
            listenerList.add(listener);
            listenerMap.put(contextPath, listenerList);
            return;
        }
        list.add(listener);
    }

    private static void triggerListener(String action, HttpSession session) {
        String contextPath = jsessionid_contextPath_currentMap.get(session.getId());
        List<HttpSessionListener> listeners = listenerMap.get(contextPath);
        if (listeners == null) {
            return;
        }
        SessionEvent event = new SessionEvent(session);
        for (HttpSessionListener listener : listeners) {
            if (action.equals("init")) {
                listener.sessionCreated(event);
            } else if (action.equals("destroy")) {
                listener.sessionDestroyed(event);
            }
        }
    }

    public static HttpSession takeOutSessionFromMap(String id) {
        if (id == null || id.trim().equals("")) {
            return null;
        }
        return currentSessionMap.get(id);
    }

    public static HttpSession getSession(Request request, Response response) {
        String sessionId = request.getRequestedSessionId();
        // 取不到id就new一个 return
        if (sessionId == null || sessionId.trim().equals("")) {
            return newSession(request, response);
        }
        // 如果用id也取不到，new一个，return
        HttpSession session = currentSessionMap.get(sessionId);
        if (session == null) {
            return newSession(request, response);
        }
        // 如果取得到，设置最后一次使用时间，从新添加到cookie，return。
        session.setMaxInactiveInterval(defaultTimeout);
        addSessionIdToCookie(session, request, response);
        return session;
    }

    private static synchronized HttpSession newSession(Request request, Response response) {
        ServletContext context = request.getServletContext();
        String id = generateSessionId();
        StandardSession session = new StandardSession(id, context);
        session.setMaxInactiveInterval(defaultTimeout);
        addSessionIdToCookie(session, request, response);
        currentSessionMap.put(id, session);

        // 清除过期的jsessionid，建立新的session与context的索引
        String contextPath = request.getContextPath();
        String oldSessionId = contextPath_jsessionid_currentMap.get(contextPath);
        if (oldSessionId != null) {
            jsessionid_contextPath_currentMap.remove(oldSessionId);
        }
        contextPath_jsessionid_currentMap.put(contextPath, id);
        jsessionid_contextPath_currentMap.put(id, contextPath);

        triggerListener("init", session);
        return session;
    }

    private static void addSessionIdToCookie(HttpSession session, Request request, Response response) {
        Cookie cookie = new Cookie("JSESSIONID", session.getId());
        cookie.setMaxAge(session.getMaxInactiveInterval());
        cookie.setPath(request.getContext().getPath());
        response.addCookie(cookie);
    }

    private static synchronized String generateSessionId() {
        String result = null;
        byte[] bytes = RandomUtil.randomBytes(16);
        result = new String(bytes);
        result = result + new Date().getTime();
        result = SecureUtil.md5(result);
        result = result.toUpperCase();
        try {
            Thread.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return result;
    }
}
